package org.infodavid.common.services;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.persistence.PersistenceException;
import javax.security.auth.login.AccountException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

import org.infodavid.common.dto.IUserConnectionListItem;
import org.infodavid.common.dto.IUserListItem;
import org.infodavid.common.dto.IUserSessionListItem;
import org.infodavid.common.dto.IUserGroupListItem.EUserRole;
import org.infodavid.common.impl.services.validators.DefaultUserValidator;
import org.infodavid.common.model.IGenericProperty;
import org.infodavid.common.model.IUser;
import org.infodavid.common.model.IUserConnection;
import org.infodavid.common.model.ModelFactory;
import org.infodavid.common.model.criteria.IExtendedSearchCriteria;
import org.infodavid.common.model.criteria.IGenericPropertyCriteria;
import org.infodavid.common.model.criteria.IUserConnectionCriteria;
import org.infodavid.common.model.criteria.IUserCriteria;
import org.infodavid.common.model.criteria.IUserSessionCriteria;
import org.infodavid.common.notifier.exceptions.NotifierException;
import org.infodavid.common.persistence.UserSessionDataService;
import org.infodavid.common.reflect.InstantiationHelper;
import org.infodavid.common.services.exceptions.ServiceException;

/**
 * The Class UserService.
 */
public abstract class UserService extends AbstractEntityService<IUserListItem,IUser,IUserCriteria> {

	/** The administrator email property. */
	public static final String ADMINISTRATOR_EMAIL_SYSTEM_PROPERTY = UserService.class.getPackage()
	    .getName() + '.' + IUser.ADMINISTRATOR + ".email";

	/** The Constant log. */
	private static final Log LOGGER = LogFactory.getLog(UserService.class);

	/** The singleton. */
	private static final UserService SINGLETON;

	static {
		try {
			SINGLETON = InstantiationHelper.getInstance().newInstance(UserService.class);
		}
		catch (final Exception e) {
			LOGGER.fatal("Can not instantiate the " + UserService.class.getName(), e);

			throw new Error(e);
		}
	}

	/**
	 * returns the singleton.
	 * @return <code>UserService</code>
	 */
	public static UserService getInstance() {
		return SINGLETON;
	}

	/**
	 * Instantiates a new service.
	 */
	protected UserService() {
		super(IUser.class, new DefaultUserValidator(), ModelFactory.getInstance().getBuilder(
		    IUser.class));
	}

	/**
	 * Gets the user session idle time in minutes.
	 * @return the default duration
	 */
	public int getSessionIdleTimeInMin() {
		return UserSessionDataService.getInstance().getSessionIdleTimeInMin();
	}

	/**
	 * Authenticate using user key. No new user session will be created.
	 * @param properties the properties
	 * @param key the user identifier
	 * @return the context
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 * @throws AccountException the account exception
	 */
	public abstract IApplicationContext authenticate(Map<String,Serializable> properties, Long key)
	    throws ServiceException, PersistenceException, AccountException;

	/**
	 * Authenticate. A user session will be retrieved using the token and will be associated to the
	 * context.
	 * @param properties the properties
	 * @param sessionKey the authentication session identifier
	 * @return the context
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 * @throws AccountException the account exception
	 */
	public abstract IApplicationContext authenticate(Map<String,Serializable> properties,
	    String sessionKey) throws ServiceException, PersistenceException, AccountException;

	/**
	 * Authenticate as system. No user session is created.
	 * @param properties the properties
	 * @return the context
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 * @throws AccountException the account exception
	 */
	public abstract IApplicationContext authentication(Map<String,Serializable> properties)
	    throws ServiceException, PersistenceException, AccountException;

	/**
	 * Change password.
	 * @param context the context
	 * @param userKey the key
	 * @param newPassword the new password
	 * @param oldPassword the old password
	 * @return user the user
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 * @throws NotifierException the notifier exception
	 */
	public abstract IUser changePassword(IApplicationContext context, Long userKey,
	    String oldPassword, String newPassword) throws ServiceException, PersistenceException,
	    NotifierException;

	/**
	 * Find the entity by name.
	 * @param context the context
	 * @param value the email
	 * @return the entity
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract IUser findByEmail(IApplicationContext context, String value)
	    throws ServiceException, PersistenceException;

	/**
	 * Find the users by their group.
	 * @param context the context
	 * @param groupKey the key of the group
	 * @param entities the entities
	 * @param criteria the criteria
	 * @return the number of results
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract long findByGroup(IApplicationContext context, Long groupKey,
	    List<IUserListItem> entities, IExtendedSearchCriteria criteria) throws ServiceException,
	    PersistenceException;

	/**
	 * Find the entity by the login name.
	 * @param context the context
	 * @param value the login name
	 * @return the entity
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract IUser findByName(IApplicationContext context, String value)
	    throws ServiceException, PersistenceException;

	/**
	 * Find the users by the type of the groups they belong.
	 * @param context the context
	 * @param role the role of the groups
	 * @param results the list of users
	 * @param criteria the criteria
	 * @return the number of elements
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract long findByRole(IApplicationContext context, EUserRole role, List<IUser> results,
	    IExtendedSearchCriteria criteria) throws ServiceException, PersistenceException;

	/**
	 * Gets the connections.
	 * @param context the context
	 * @param entities the results
	 * @param criteria the criteria
	 * @return the number of connections
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract long findConnections(IApplicationContext context,
	    List<IUserConnectionListItem> entities, IUserConnectionCriteria criteria)
	    throws ServiceException, PersistenceException;

	/**
	 * Gets the sessions.
	 * @param context the context
	 * @param entities the results
	 * @param criteria the criteria
	 * @return the number of sessions
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract long findSessions(IApplicationContext context,
	    List<IUserSessionListItem> entities, IUserSessionCriteria criteria) throws ServiceException,
	    PersistenceException;

	/**
	 * Find roles according to the roles of the groups.
	 * @param context the context
	 * @param userKey the key
	 * @param results the results
	 * @return the highest role
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract EUserRole findRoles(IApplicationContext context, Long userKey,
	    List<EUserRole> results) throws ServiceException, PersistenceException;

	/**
	 * Find system user.
	 * @param context the context
	 * @return the i user
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract IUser findSystemUser(IApplicationContext context) throws ServiceException,
	    PersistenceException;

	/**
	 * Gets the locale.
	 * @param context the context
	 * @param user the user
	 * @return the locale
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract Locale getLocale(IApplicationContext context, IUser user)
	    throws ServiceException, PersistenceException;

	/**
	 * Gets the password policy.
	 * @return the password policy
	 */
	public abstract IPasswordPolicy getPasswordPolicy();

	/**
	 * Gets the time zone.
	 * @param context the context
	 * @param user the user
	 * @return the time zone
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract DateTimeZone getTimeZone(IApplicationContext context, IUser user)
	    throws ServiceException, PersistenceException;

	/**
	 * Checks for role.
	 * @param context the context
	 * @param userKey the user key
	 * @param role the role
	 * @return true, if successful
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract boolean hasRole(IApplicationContext context, long userKey, EUserRole role)
	    throws ServiceException, PersistenceException;

	/**
	 * Checks if is connection of.
	 * @param context the context
	 * @param connectionKey the connection key
	 * @param userKey the user key
	 * @return true, if is connection of
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract boolean isConnectionOf(IApplicationContext context, Long connectionKey,
	    Long userKey) throws ServiceException, PersistenceException;

	/**
	 * Register.
	 * @param context the context
	 * @param user the user
	 * @return the key of the registered user
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 * @throws NotifierException the notifier exception
	 */
	public abstract long register(IApplicationContext context, IUser user) throws ServiceException,
	    PersistenceException, NotifierException;

	/**
	 * Reset user account.
	 * @param context the context
	 * @param email the user email
	 * @return user the user
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 * @throws NotifierException the notifier exception
	 */
	public abstract IUser reset(IApplicationContext context, String email) throws ServiceException,
	    PersistenceException, NotifierException;

	/**
	 * Login, first authentication of the user with update of some data of the profile and user
	 * session creation. The new user session will be associated to the context.
	 * @param properties the properties
	 * @param login the login
	 * @param password the password
	 * @return the context
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 * @throws AccountException the account exception
	 */
	public abstract IApplicationContext signIn(Map<String,Serializable> properties, String login,
	    String password) throws ServiceException, PersistenceException, AccountException;

	/**
	 * Sign out. The user session will be invalidated without update on it and application context
	 * will be cleaned up.
	 * @param context the context
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract void signOut(IApplicationContext context) throws ServiceException,
	    PersistenceException;

	/**
	 * Submit connection to.
	 * @param context the context
	 * @param connections the connections to submit
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 * @throws NotifierException the notifier exception
	 */
	public abstract void submitConnections(IApplicationContext context,
	    Collection<IUserConnection> connections) throws ServiceException, PersistenceException,
	    NotifierException;

	/**
	 * Gets the connections.
	 * @param context the context
	 * @param key the key
	 * @return the user connection
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 */
	public abstract IUserConnection getConnection(IApplicationContext context, long key)
	    throws ServiceException, PersistenceException;

	/**
	 * Update connections.
	 * @param context the context
	 * @param connections the connections
	 * @throws ServiceException the service exception
	 * @throws PersistenceException the persistence exception
	 * @throws NotifierException the notifier exception
	 */
	public abstract void updateConnections(IApplicationContext context,
	    Collection<IUserConnection> connections) throws ServiceException, PersistenceException,
	    NotifierException;

	/**
	 * Removes the connection.
	 * @param context the context
	 * @param key the key
	 * @throws PersistenceException the persistence exception
	 * @throws ServiceException the service exception
	 */
	public abstract void removeConnection(final IApplicationContext context, final long key)
	    throws PersistenceException, ServiceException;

	/**
	 * Find the properties using the scope.
	 * @param context the context
	 * @param results the resulting properties
	 * @param userKey the user key
	 * @param criteria the criteria
	 * @throws PersistenceException the persistence exception
	 * @throws ServiceException the service exception
	 */
	public abstract void findProperties(IApplicationContext context,
	    Collection<IGenericProperty> results, long userKey, IGenericPropertyCriteria criteria)
	    throws PersistenceException, ServiceException;

	/**
	 * Sets the properties.
	 * @param context the context
	 * @param userKey the user key
	 * @param properties the properties to update
	 * @return the modification associated to the application settings entity
	 * @throws PersistenceException the persistence exception
	 * @throws ServiceException the service exception
	 */
	public abstract DateTime updateProperties(IApplicationContext context, long userKey,
	    Collection<IGenericProperty> properties) throws PersistenceException, ServiceException;
}
